'''
(c) 2008 Adam Pridgen adam@thecoverofnight.com,
The Cover of Night, LLC
zone_xfr_test.py 

Script will gather information using nmblookup,smbclient, nbtstat, nmbscan

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.


    Version .01b - initial release of code
    Version .02b - Added patch to handle IP List Ranges from Riley Porter
'''

print '''If used commercially, the tool name and web address must be mentioned in the report along with the following projects.

This tool uses data from the following tools:
Samba Suite http://us6.samba.org/samba/
nmbscan http://freshmeat.net/projects/nmbscan/
nbtscan http://www.inetcat.net/software/nbtscan.html
'''


import sys, pexpect, codecs, strings



class NetBiosInfo:
  def __init__(self, string=None, csv=None):

    self.ip_address = ''
    self.mac_address = ''
    self.description = ''
    self.domain = ''
    self.numMacs = 0
    self.numIPs = 0
    self.addresses = []
    self.ip_addrs = set()
    self.mac_addrs = set()
    self.os = ''
    self.domain = ''
    self.server_software = ''
    self.nmblookup = ''
    self.netbios_identifier = ''
    self.netbios_name = ''
    if csv:
       self.csvString(string)
       return
    if string:
       self.netbios_name = string.split()[0]
    if string and len(string.split()) > 1:
       self.description = " ".join(string.split()[1:])
    string = None
  def setNmblookup(self, string):
    self.nmblookup = string
    if self.nmblookup.find("name_query failed to find name") > -1:return
    k = self.nmblookup.split("\r\n")[1]
    self.ip_address = k.split()[0]
    self.netbios_identifier = '<'+"<".join(k.split('<')[1:])
  def csvString(self, string):
    s = string.split(',')
    self.ip_address = s[0]
    self.netbios_name = s[1]
    self.description = s[2]
    self.netbios_identifier = s[3]
  def getAddresses(self, s):
    ips = ''
    macs = ''
    if len(s.split('ip-address')) > 1:
      ips = 'ip-address '+"ip-address".join(s.split('ip-address')[1:])
      ips = ips.split("server-software ")[0]
    if len(s.split('mac-address')) > 1:
      macs = 'mac-address '+"mac-address".join(s.split('mac-address')[1:])
      macs = macs.split("server-software ")[0]
    macs = [i.split('\n')[0] for i in macs.split("mac-address")]
    ips = [i.split('\n')[0] for i in ips.split("ip-address")]
    t_ips = []
    for i in ips:
      if i == '': continue
      t_ips.append(i.strip().lstrip())
    t_macs = []
    for i in macs:
      if i == '': continue
      t_macs.append(i.strip().lstrip())
    for i in xrange(0, min(len(t_ips),len(t_macs))):
      self.addresses.append((t_ips[i], t_macs[i]))
  def setNmbscan(self, nbtscan_string):
    s = nbtscan_string.split("\n")
    self.getAddresses(s)
    # Set stuff that is not ip-address related
    for i in s.split('\n'):
      if len(i.split()) < 2: continue
      if i.split()[0] == 'domain':
        domain = i.split()[1]
      elif i.split()[0] == 'server-software':
        server_software = " ".join(i.split()[1:])
      elif i.split()[0] == 'operating-system':
        os = " ".join(i.split()[1:])
  def setNbtscan(self, data):
    i = data.split()
    if len(i) == 0:  return
    if len(i) < 3:
	self.addAddrs(i[0], "<unknown>")
	self.netbios_name = i[0]
	self.netbios_identifier = "<unknown>"
	return
    if len(i) == 4:
    	self.addAddrs(i[0], i[-1])
    else:
    	self.addAddrs(i[0], "<unknown>")
    self.netbios_name = i[1]
    self.netbios_identifier = i[2]
  def addAddrs(self, ip, mac):
    if not ip in self.ip_addrs and\
       not mac in self.mac_addrs:
       self.addresses.append((ip,mac))
    if not ip in self.ip_addrs:
      self.ip_addrs.add(ip)
      self.numIPs += 1
      self.ip_address = ip
    if not mac in self.mac_addrs:
      self.mac_addrs.add(ip)
      self.numMacs += 1
      self.mac_address = mac

def unmannedNbtscan(data, filename):
  f = performMultiNbtscan(data)
  g = []
  for k,v in f.items():
    if k == "<unknown>" or k == "": continue
    g.append(v.ip_address+" "+v.netbios_name)
  o = open(filename,'w')
  o.write("\n".join(g))
  o.close()
  return g

def condenseNbtscan(results, result):
  for k,v in result.items():
    if not k in results:
      results[k] = v
      continue
    if k in results:
      for i in v.ip_addrs:
        if i in results[k].ip_addrs:
	  results[k].ip_addrs.add(i)
	  results[k].numIPs += 1
  return results

def performMultiNbtscan(data):
  nbtscan = {}
  for i in data:
    print "Nbtscanning %s"%i
    result = getNbtscan(i)
    result = parseNbtscan(result)
    nbtscan = condenseNbtscan(nbtscan, result)
  return nbtscan

def parseNbtscan(data):
  s = "-"*78+'\r\n'
  d = data.split(s)
  nb_data = {}
  if len(d) != 2: return nb_data
  d = d[1] 
  entrys = d.split('\r\n')
  for i in entrys:
    n = NetBiosInfo()
    n.setNbtscan(i)
    if n.netbios_name in nb_data:
      for ip, mac in n.addresses:
	 nb_data[n.netbios_name].addAddrs(ip, mac)
      continue
    nb_data[n.netbios_name] = n
  return nb_data

def getNbtscan(ip):
  '''  Perform Nbtscan for the netbios names
       @param ip: ip address or network to scan for names
       @retval result: nmblookup string
  '''
  nbtscan = 'nbtscan -r -t 5000 %s'
  lookup = pexpect.spawn(nbtscan%(ip), timeout=160)
  try:
  	result = lookup.read()
  except:
	return ""
  return result


def get_csv(nb_entry):
     s = []
     s.append(nb_entry.ip_address)
     s.append(nb_entry.netbios_name)
     s.append(nb_entry.description)
     s.append(nb_entry.netbios_identifier)
     return ",".join(s)
 
# parse from smbclient -L <domain_controller>, and the host list
# Step 1, get all the hosts in the domain to query
def getSmbInfo(host):
  cmd = "smbclient -L %s -N"%host
  smbclient = pexpect.spawn(cmd, timeout=60)
  result = smbclient.read()
  hosts = getSmbHosts(result)
  wgs = getSmbWorkgroups(result)
  return hosts,wgs,result

def getSmbHosts(data):
  ''' Parses out all of the hosts from the smbclient output.
      @param data: string of smbclient output
      @retval host: list of hosts and descriptions
  '''
  smb_str = "\r\n\r\n\tServer               Comment\r\n\t---------            -------\r\n"
  hosts = data.split(smb_str)[1].split("\r\n\r\n\t")[0]
  hosts = [i.strip().lstrip() for i in hosts.split("\r\n")]
  return hosts

def getSmbWorkgroups(data):
  ''' Parses out all of the workgroups from the smbclient output.
      @param data: string of smbclient output
      @retval host: list of workgroups and descriptions
  '''
  smb_str = "\r\n\r\n\tWorkgroup            Master\r\n\t---------            -------\r\n"
  wgs = data.split(smb_str)[1].split("\r\n\r\n\t")[0]
  wgs = [i.strip().lstrip() for i in wgs.split("\r\n")]
  return wgs

def getSmbShares(data):
  ''' Parses out all of the hosts from the smbclient output.
      @param data: string of smbclient output
      @retval host: list of hosts and descriptions
  '''
  smb_str = "\tSharename       Type      Comment\r\n\t---------       ----      -------\r\n"
  shares = data.split(smb_str)[1].split("\r\n\r\n")[0]
  shares = [i.strip().lstrip() for i in shares.split("\r\n")]
  return shares

def setNetbiosInfos(hosts):
  ''' Set the netbios host names and descriptions from the pasrsed smbclient info.
      @param hosts: list of netbios names and netbios descriptions
      @retval results: list of netbios instances
  '''
  results = []
  for i in hosts:
    n = NetBiosInfo(i)
    results.append(n)
  return results

def parseSmbclientData(data):
  ''' Parses out all of the workgroups and hosts from the smbclient output.
      justs combines the functionality
      @param data: smbclient data
      @retval results: list of netbios names and descriptions
  '''
  host = getSmbHosts(data)
  wgs = getSmbWorkgroups(data)
  return hosts, wgs




def obtainDomainNetbiosInfo(DC):
  '''Umbrella function that does everything.  It only requires
     the current domain controller
     1.  get hosts from the data share by logging into the anonymous share
     2.  set all the netbios information from 
     3.  use nmmlookup to get the ip address and the identifier
     4.  TODO: get OS information and Mac address info from the nmbscan info  
     5.  TODO: print "Identifying machines in the domain."
  '''
  hosts,wgs,result = getSmbInfo(DC)
  print "Setting the Net Bios  machines information."
  netbios_infos = setNetbiosInfos(hosts)
  print "Setting nmblookup info."
  performNmblookup(DC, netbios_infos)
  return netbios_infos,hosts,wgs,result


def performNmbscan(netbios_infos):
  '''  Scan hosts and get operating system information, server software, domain,
       mac addresses from hosts.
       @param netbios_infos: list of netbios instances
       @retval result: returns a list of strings that are the nmbscan data 
  '''
  results = []
  for i in netbios_infos:
    results.append(getNmbscan(i.ip_address))
  return results

def getNmbscan(ipaddress):
  '''  Perform the nmbscan and get all the client information for the specified ipaddress
       @param ipaddress: the ipaddress to scann
       @retval result: string containing the results of the scan
  '''
  cmd = "nmbscan -h %s -a"%ipaddress
  result = ''
  try:
    nmbscan = pexpect.spawn(cmd, timeout=60)
    result = nmbscan.read()
    nmbscan.close()
  except: pass
  return result


def performNmbscanbyIP(hosts):
  '''  Scan hosts and get operating system information, server software, domain,
       mac addresses from hosts.
       @param hosts: list of hosts, which parsed is parsed smbclient info
       @retval results: returns a list of strings that are the nmbscan data 
  '''
  results = []
  for i in hosts:
    if len(i.split())!=2: continue
    print i.split()[0]
    r = getNmbscan(i.split()[0])
    if r == '': continue
    results.append(r)
  return results

def getNmblookup(DC, netbios_name):
  '''  Perform Nmblookup for the netbios names
 nbtscan -r -t 5000      @param DC: domain controller ip address
       @param netbios_infos: list of netbios instances
       @retval result: nmblookup string
  '''
  nmblookup = "nmblookup -U %s -R '%s'"
  lookup = pexpect.spawn(nmblookup%(DC, netbios_name), timeout=60)
  result = lookup.read()
  return result
  


def performNmblookup(DC, netbios_infos):
  '''  Perform Nmblookup for the list of netbios instances
       @param DC: domain controller ip address
       @param netbios_infos: list of netbios instances
       @retval None: the values are set in the netbios_infos
  '''
  for host in netbios_infos:
    result = getNmblookup(DC, host.netbios_name)
    host.setNmblookup(result)

def printDomainInfo(netbios_infos=None,hosts=None,fname=None):
  '''Utility function to print out all the data.  Prints the host data
     from the parsed smbclient data.  prints the netbios_infos in csv
     format.
     @param hosts: string of hosts, flushed directly to file
     @param netbios_infos: list of netbios instances, and the data is written as csv
     @param fname: base file name to write too
  '''
  out = None
  if fname:
    out = codecs.open(fname+"_netbios_info.txt", 'w')
  else:
    out = sys.stdout
  file_results = []
  print "Writing Netbios Information"
  for i in netbios_infos:
    out.write(get_csv(i).decode("utf-8")+u'\n')
  out.close()
  if fname:
    out = codecs.open(fname+"_domain_hosts.txt", 'w')
  else:
    out = sys.stdout
  print "Writing hosts to the file"
  for i in hosts:
    out.write(hosts.decode("utf-8")+u'\n')
  out.close()

def generic_write(some_list, fname):
  '''Just a generic write fucntion with a delimiter I can parse
  '''
  import codecs
  out = None
  if fname:
    out = codecs.open(fname+".txt", 'a')
  else:
    out = sys.stdout
  for i in some_list:
    out.write("*"*40+'\n')
    out.write(i.decode("utf-8")+u'\n')
    out.write("*"*40+'\n')
  out.close()




def getips(IP_RANGE):
    '''
    getips(IPRange)
    
    example usage:
    IP_RANGE = '192.168.1.1-50'

    x = getips(IP_RANGE)
    for ip in x:
        print x
    '''
    List = []
    IP_LIST = []
    IP_RANGE = IP_RANGE.rsplit(".",2) # breaks up IP into a list with the Following Values ['192.168','1','1-50']
    print IP_RANGE[1]
    if len(IP_RANGE[1].split("-",1)) ==2: #Checks if where range identifiers are located "-"
        for i in range(int(IP_RANGE[1].split("-",1)[0]),int(IP_RANGE[1].split("-",1)[1])+1,1):
            List.append(IP_RANGE[0]+"."+str(i)+".")
        for ip in List:
            for i in range(int(IP_RANGE[2].split("-",1)[0]),int(IP_RANGE[2].split("-",1)[1])+1,1):
                IP_LIST.append(ip+str(i))
        return IP_LIST
    if len(IP_RANGE[1].split("-",1)) ==1:
        for i in range(int(IP_RANGE[2].split("-",1)[0]),int(IP_RANGE[2].split("-",1)[1])+1,1):
            IP_LIST.append(IP_RANGE[0]+"."+str(IP_RANGE[1].split("-",1)[0])+"."+str(i))
        return IP_LIST


#DC = 'IP_ADDRESS_HERE'

#netbios_infos,hosts,wgs,domain_list = getSmbInfo(DC)
#nmbscan = performNmbscan(netbios_infos)
#printDomainInfo(netbios_infos,hosts,"domain_info")
#generic_write(nmbscan, "nmbscan")


#print "How I write the csv file."
#file_results = []
#for i in netbios_infos:
#  file_results.append(get_csv(i))

#out_file = codecs.open("host_csv.txt", 'w')
#for i in file_results:
#  out_file.write(i.decode("utf-8")+u"\n")
#out_file.close()

